//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/Systems/BatchRender.h"

namespace Engine
{
namespace Graphics
{
	/*
		struct EmitterStrategy
		{
			void Clear () {}
			bool Update (float dt, Particle &p)	{}	// returns true if particle is active
			void Draw (const Particle &p, Primitive &pr, uint16 *indices, uint indexOffset) {}
			void Emit (Particle &p, uint index) {}
		};
	*/



	//
	// Particle System
	//

	template <typename TParticle, typename TPrimitive, typename TStrategy>
	class ParticleSystem : public BaseObject
	{
	// types
	public:
		typedef TStrategy			Strategy;
		typedef TParticle			Particle;
		typedef TPrimitive			Primitive;

		typedef ParticleSystem< TParticle, TPrimitive, TStrategy >		Self;


	private:
		typedef array< Primitive >		primitives_t;
		typedef array< Particle >		particles_t;
		typedef uint16_array_t			indices_t;


	// variables
	private:
		// emitter
		Strategy		_strategy;
		float			_freq;

		BatchMaterial	_material;

		GLVertexAttribs	_attribs;

		particles_t		_particles;
		primitives_t	_primitives;
		indices_t		_indices;

		float			_time;
		uint			_numParticles;
		uint			_maxPaticles;


	// methods
	public:
		ParticleSystem (const EngineSubSystemsRef ess);
		
		void Draw ();
		void Update (float dt);

		void Clear ();

		void SetMaxParticles (usize value);

		void SetMaterial (const BatchMaterial &mtr)		{ _material = mtr; }

		void SetFrequency (float freq)					{ _freq = freq; }

		Strategy &		GetStrategy ()					{ return _strategy; }
		float			GetFrequency ()		const		{ return _freq; }
		uint			GetNumParticles ()	const		{ return _numParticles; }
		uint			GetMaxParticles ()	const		{ return _maxPaticles; }


	private:
		bool _EmitParticle (Particle &particle);
	};

	

	
/*
=================================================
	constructor
=================================================
*/
	template <typename P, typename Pr, typename S>
	inline ParticleSystem<P,Pr,S>::ParticleSystem (const EngineSubSystemsRef ess) :
		BaseObject(ess), _freq(0.0f), _numParticles(0), _maxPaticles(0), _time(0.0f)
	{
		_attribs = Primitive::vertex_t::GetAttribs();

		// some primitive types not supported
		ASSERT( sizeof(Primitive) == sizeof(typename Primitive::vertex_t) * Primitive::VerticesCount() );
	}
	
/*
=================================================
	SetMaxParticles
=================================================
*/
	template <typename P, typename Pr, typename S>
	inline void ParticleSystem<P,Pr,S>::SetMaxParticles (usize value)
	{
		ASSERT( value <= 0xFFFF );

		_maxPaticles = value;

		_particles.Resize( value, false );
		_primitives.Reserve( value );
		_indices.Reserve( value * Primitive::IndexCount() );
	}
	
/*
=================================================
	Update
=================================================
*/
	template <typename P, typename Pr, typename S>
	inline void ParticleSystem<P,Pr,S>::Update (float dt)
	{
		usize	j = 0;

		_time += dt;

		_numParticles = uint( _time * _freq + 0.5f );

		_time -= _numParticles / _freq;

		const uint	count = _primitives.Count();

		_indices.SetCount( _maxPaticles * Primitive::IndexCount() );
		_primitives.SetCount( _maxPaticles );

		FOR( i, _particles )
		{
			if ( ( /*i >= count or*/ not _strategy.Update( dt, _particles[i] ) ) and
				 not _EmitParticle( _particles[i] ) )
			{
				continue;
			}

			_strategy.Draw( _particles[i], _primitives[j], _indices.ptr(), j );
			++j;
		}
		
		_indices.SetCount( j * Primitive::IndexCount() );
		_primitives.SetCount( j );

		_numParticles = 0;
	}
	
/*
=================================================
	Draw
=================================================
*/
	template <typename P, typename Pr, typename S>
	inline void ParticleSystem<P,Pr,S>::Draw ()
	{
		StateManager * sm = ESS()->GetGLStateManager();

		_material.Apply( sm );

		sm->BindVertexArray( _attribs, (uint)_primitives.ptr() );

		sm->BindIndexBuffer( 0 );

		if ( Primitive::IndexCount() == 0 )
		{
			sm->DrawArrays( Primitive::Primitive(), 0, _primitives.Count() );
		}
		else
		{
			sm->DrawElements( Primitive::Primitive(), _indices.Count(), gl_index::UINT16,
								(const uint8 *)_indices.ptr() );
		}
	}

/*
=================================================
	_EmitParticle
=================================================
*/
	template <typename P, typename Pr, typename S>
	inline bool ParticleSystem<P,Pr,S>::_EmitParticle (Particle &particle)
	{
		if ( _numParticles == 0 )
			return false;

		--_numParticles;

		_strategy.Emit( particle, _numParticles );
		return true;
	}
	
/*
=================================================
	Clear
=================================================
*/
	template <typename P, typename Pr, typename S>
	inline void ParticleSystem<P,Pr,S>::Clear ()
	{
		_strategy.Clear();
		_material.Clear();

		_particles.ClearMem();
		_primitives.ClearMem();
		_indices.ClearMem();

		_attribs		= GLVertexAttribs();

		_freq			= 0.0f;
		_time			= 0.0f;
		_maxPaticles	= 0;
		_numParticles	= 0;
	}


}	// Graphics
}	// Engine